package tool.analyzer
 
object Types {
	import Symbols._
	import Types._

	sealed abstract class Type {
		def isSubTypeOf(tpe: Type): Boolean
	}
 
	// having this "bottom" class (which extends every other one) can be convenient for error recovery...
	case object TError extends Type {
		override def isSubTypeOf(tpe: Type): Boolean = true
		override def toString = "[error]"
	}
  
	// the default type for all Typed objects
	case object TUntyped extends Type {
		override def isSubTypeOf(tpe: Type): Boolean = false 
		override def toString = "[untyped]"
	}

	/**
	  * Primary types
	  */
	case object TInt extends Type {
		override def isSubTypeOf(tpe: Type): Boolean = tpe match{
			case TInt => true 
			case _ => false
		}
    	override def toString = "int"
	}
  
	case object TBool extends Type{
		override def isSubTypeOf(tpe: Type): Boolean = tpe match{
			case TBool => true
			case _ => false
		}
		override def toString = "bool"
	}

	case object TArray extends Type{
		override def isSubTypeOf(tpe: Type): Boolean = tpe match{
			case TArray => true
			case _ => false
		}
		override def toString = "int[]"
	}

	case object TString extends Type{
		override def isSubTypeOf(tpe: Type): Boolean = tpe match{
			case TString => true
			case _ => false
		}
	}

  
	// It's convenient to reference classes by their symbol, rather than by their name, to avoid doing the lookup every time.
	case class TObject(classSymbol: ClassSymbol) extends Type {
		override def isSubTypeOf(tpe: Type): Boolean = {
			tpe match{
				case TObject(cl) => 
					if(cl.name == "Object")
						return true
					var currSymbol = classSymbol
					while(currSymbol.name != cl.name){
						currSymbol.parent match{
							case Some(p) => currSymbol = p
							case None => return false
						}
					}
					true
				case _ => false
			}
		}
		override def toString = classSymbol.name
	}
  
	// special object to implement the fact that all objects are its subclasses.
	val anyObject = TObject(new ClassSymbol("Object"))
  
  /** Add this trait to all tree nodes which represent typed expressions.
    * This will be needed for code generation. Also add it to VariableSymbol and
    * MethodSymbol, to represent the type (or the return type) of these symbols.
    * Note that your Identifier tree node has now potentially a lot of redundant
    * information. A good idea is to override the setType and getType methods in
    * Identifier so that setting a type is impossible (it should always be done
    * through a symbol), and getting a type returns a result which depends on what
    * the identifier represents (what its symbol is). */

	trait Typed {
    	self =>
    
    	private var _tpe: Type = TUntyped
    
    	def setType(tpe: Type): self.type = { _tpe = tpe; this }
    	def getType: Type = _tpe
  	}
}
